package com.cloud.gateway.core;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.cloud.gateway.bean.ReleaseInfo;
import com.cloud.gateway.context.AppContext;
import com.cloud.gateway.limiter.LimiterDefinition;
import com.cloud.gateway.limiter.LimiterManager;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.filter.factory.HystrixGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactory;
import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.CachingRouteLocator;
import org.springframework.cloud.gateway.route.InMemoryRouteDefinitionRepository;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.nio.charset.Charset;
import java.util.*;

@Slf4j
@Component
public class ConfigLoader {

    @Value("${spring.application.name}")
    private String appName;

    @Value("${spring.cloud.config.uri}")
    private String configUri;

    @Value("${spring.cloud.config.label}")
    private String configLable;

    @Value("${spring.cloud.config.username}")
    private String configUsername;

    @Value("${spring.cloud.config.password}")
    private String configPassword;

    @Autowired
    private LimiterManager limiterManager;

    private List<ReleaseInfo> releaseInfoList;

    private Set<String> excludePaths;

    /**
     * 加载
     */
    public void load() {
        //组装请求地址
        StringBuilder requestUrl = new StringBuilder(configUri);
        requestUrl.append("/").append(appName).append("/default").append("/").append(configLable);

        //调用配置中心
        String authStr = configUsername + ":" + configPassword;
        HttpHeaders headers = new HttpHeaders();
        headers.set("authorization", "Basic " + new String(Base64.encodeBase64(authStr.getBytes(Charset.forName("US-ASCII")))));
        String result = new RestTemplate().exchange(requestUrl.toString(), HttpMethod.GET, new HttpEntity<>(null, headers), String.class).getBody();

        //读取配置属性
        JSONArray propertySources = JSON.parseObject(result).getJSONArray("propertySources");
        if (propertySources.size() == 0) {
            return;
        }
        JSONObject properties = propertySources.getJSONObject(0).getJSONObject("source");
        log.info("load >>> {}", properties.toJSONString());

        //加载路由
        loadRoutes(properties);

        //加载限流
        loadLimiters(properties);

        //加载发布版本
        loadReleases(properties);

        //加载排除校验的路径
        loadExcludePaths(properties);
    }

    /**
     * 加载路由
     * @param properties
     */
    private void loadRoutes(JSONObject properties) {
        final String routeIdKey = "routes[@].id";
        final String routeUriKey = "routes[@].uri";
        final String routePathKey = "routes[@].path";
        final String placeHolder = "@";
        try {
            HashMap<String, RouteDefinition> routeDefinitionMap = new HashMap<>();
            for (int i = 0; i < Short.MAX_VALUE; i++) {
                String routeId = properties.getString(routeIdKey.replace(placeHolder, i + ""));
                if (StringUtils.isEmpty(routeId)) {
                    break;
                }
                String routeUri = properties.getString(routeUriKey.replace(placeHolder, i + ""));
                String routePath = properties.getString(routePathKey.replace(placeHolder, i + ""));

                HashMap predicateParams = new HashMap();
                predicateParams.put(PathRoutePredicateFactory.PATTERN_KEY, routePath);
                PredicateDefinition predicateDefinition = new PredicateDefinition();
                predicateDefinition.setName("Path");
                predicateDefinition.setArgs(predicateParams);

                HashMap filtersParams = new HashMap();
                filtersParams.put(StripPrefixGatewayFilterFactory.PARTS_KEY, "1");
                FilterDefinition stripFilterDefinition = new FilterDefinition();
                stripFilterDefinition.setName("StripPrefix");
                stripFilterDefinition.setArgs(filtersParams);

                filtersParams = new HashMap();
                filtersParams.put("name", routeId);
                filtersParams.put(HystrixGatewayFilterFactory.FALLBACK_URI, "forward:/fallback");
                FilterDefinition hystrixFilterDefinition = new FilterDefinition();
                hystrixFilterDefinition.setName("Hystrix");
                hystrixFilterDefinition.setArgs(filtersParams);

                RouteDefinition routeDefinition = new RouteDefinition();
                routeDefinition.setId(routeId);
                routeDefinition.setUri(URI.create(routeUri));
                routeDefinition.setPredicates(Arrays.asList(predicateDefinition));
                routeDefinition.setFilters(Arrays.asList(stripFilterDefinition, hystrixFilterDefinition));

                routeDefinitionMap.put(routeId, routeDefinition);
            }
            if (routeDefinitionMap.size() > 0) {
                InMemoryRouteDefinitionRepository routeDefinitionRepository = AppContext.getBean(InMemoryRouteDefinitionRepository.class);
                CachingRouteLocator cachingRouteLocator = (CachingRouteLocator) AppContext.getBean(RouteLocator.class);

                //删除当前路由
                List<RouteDefinition> list = routeDefinitionRepository.getRouteDefinitions().collectList().block();
                for (RouteDefinition routeDefinition : list) {
                    routeDefinitionRepository.delete(Mono.just(routeDefinition.getId())).subscribe();
                }

                //保存新路由
                Iterator<String> itor = routeDefinitionMap.keySet().iterator();
                while (itor.hasNext()) {
                    RouteDefinition routeDefinition = routeDefinitionMap.get(itor.next());
                    routeDefinitionRepository.save(Mono.just(routeDefinition)).subscribe();
                }

                //刷新缓存
                cachingRouteLocator.refresh();
            }
        } catch (Exception e) {
            log.error("loadRoutes error", e);
        }
    }

    /**
     * 加载限流配置
     * @param properties
     */
    private void loadLimiters(JSONObject properties) {
        final String pathKey = "limiters[@].path";
        final String capacityKey = "limiters[@].capacity";
        final String refillTokensKey = "limiters[@].refillTokens";
        final String refillPeriodKey = "limiters[@].refillPeriod";
        final String limitUserIdKey = "limiters[@].limitUserId";
        final String placeHolder = "@";
        try {
            List<LimiterDefinition> limiterDefinitionList = new ArrayList<>();
            for (int i = 0; i < Short.MAX_VALUE; i++) {
                String path = properties.getString(pathKey.replace(placeHolder, i + ""));
                if (StringUtils.isEmpty(path)) {
                    break;
                }
                String capacity = properties.getString(capacityKey.replace(placeHolder, i + ""));
                String refillTokens = properties.getString(refillTokensKey.replace(placeHolder, i + ""));
                String refillPeriod = properties.getString(refillPeriodKey.replace(placeHolder, i + ""));
                String limitUserId = properties.getString(limitUserIdKey.replace(placeHolder, i + ""));
                limitUserId = limitUserId == null ? "" : limitUserId;

                LimiterDefinition limiterDefinition = new LimiterDefinition();
                limiterDefinition.setPath(path);
                limiterDefinition.setCapacity(Long.parseLong(capacity));
                limiterDefinition.setRefillTokens(Long.parseLong(refillTokens));
                limiterDefinition.setRefillPeriod(Long.parseLong(refillPeriod));
                limiterDefinition.setLimitUserId(limitUserId);
                limiterDefinitionList.add(limiterDefinition);
            }
            limiterManager.init(limiterDefinitionList);
        } catch (Exception e) {
            log.error("loadLimiters error", e);
        }
    }

    /**
     * 加载发布信息
     * @param properties
     */
    private void loadReleases(JSONObject properties) {
        final String versionKey = "releases[@].version";
        final String userIdKey = "releases[@].userId";
        final String userAreaKey = "releases[@].userArea";
        final String placeHolder = "@";
        try {
            List<ReleaseInfo> releaseInfoList = new ArrayList<>();
            for (int i = 0; i < Short.MAX_VALUE; i++) {
                String version = properties.getString(versionKey.replace(placeHolder, i + ""));
                if (StringUtils.isEmpty(version)) {
                    break;
                }
                String userId = properties.getString(userIdKey.replace(placeHolder, i + ""));
                String userArea = properties.getString(userAreaKey.replace(placeHolder, i + ""));
                userArea = userArea == null ? "" : userArea;
                userId = userId == null ? "" : userId;

                ReleaseInfo releaseInfo = new ReleaseInfo();
                releaseInfo.setVersion(version);
                releaseInfo.setUserId(userId);
                releaseInfo.setUserArea(userArea);
                releaseInfoList.add(releaseInfo);
            }
            Collections.sort(releaseInfoList);
            this.releaseInfoList = releaseInfoList;
        } catch (Exception e) {
            log.error("loadReleases error", e);
        }
    }

    /**
     * 加载排除校验的路径
     * @param properties
     */
    private void loadExcludePaths(JSONObject properties) {
        final String excludePathsKey = "excludePaths[@]";
        final String placeHolder = "@";
        try {
            Set<String> excludePaths = new HashSet<>();
            for (int i = 0; i < Short.MAX_VALUE; i++) {
                String path = properties.getString(excludePathsKey.replace(placeHolder, i + ""));
                if (StringUtils.isEmpty(path)) {
                    break;
                }
                excludePaths.add(path);
            }
            this.excludePaths = excludePaths;
        } catch (Exception e) {
            log.error("loadExcludePaths error", e);
        }
    }

    public Set<String> getExcludePaths() {
        return excludePaths;
    }

    public List<ReleaseInfo> getReleaseInfoList() {
        return releaseInfoList;
    }
}
